﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Data;
using System.Globalization;

namespace AirLinq.Providers.Sql {
	public class CommandGenerator : SqlVisitor {
		private bool _injectNot;

		private readonly StringBuilder _builder = new StringBuilder();
		private readonly List<System.Data.SqlClient.SqlParameter> _parameters = new List<System.Data.SqlClient.SqlParameter>();
		private StringBuilder Builder {
			get {
				return _builder;
			}
		}
		public List<System.Data.SqlClient.SqlParameter> Parameters {
			get {
				return _parameters;
			}
		}
		private string GetJoinString(JoinType joinType) {
			switch (joinType) {
				case JoinType.Inner: return "INNER JOIN";
				case JoinType.LeftOuter: return "LEFT OUTER JOIN";
				case JoinType.Full: return "FULL OUTER JOIN";
				case JoinType.Cross: return "CROSS JOIN";
				default: throw new InvalidOperationException("join type not supported");
			}
		}
		private string GetNodeString(SqlNodeType nodeType) {
			switch (nodeType) {
				case SqlNodeType.EQ: return "=";
				case SqlNodeType.NotEQ: return "<>";
				case SqlNodeType.GT: return ">";
				case SqlNodeType.GE: return ">=";
				case SqlNodeType.LT: return "<";
				case SqlNodeType.LE: return "<=";

				case SqlNodeType.And: return "AND";
				case SqlNodeType.Or: return "OR";

				case SqlNodeType.Add: return "+";
				case SqlNodeType.Sub: return "-";
				case SqlNodeType.Mul: return "*";
				case SqlNodeType.Div: return "/";
				case SqlNodeType.Mod: return "%";
				case SqlNodeType.BitAnd: return "&";
				case SqlNodeType.BitOr: return "|";
				case SqlNodeType.BitXor: return "^";

				case SqlNodeType.Union: return "UNION";
				case SqlNodeType.UnionAll: return "UNION ALL";
				case SqlNodeType.Except: return "EXCEPT";
				case SqlNodeType.Intersect: return "INTERSECT";

				default: throw new InvalidOperationException("comparision type not supported");
			}
		}
		private int GetLevel(SqlNode node) {
			if (node == null) {
				return 0;
			}
			switch (node.NodeType) {
				case SqlNodeType.Parameter:
				case SqlNodeType.SelectItemRef:
				case SqlNodeType.Column: return 0;
				case SqlNodeType.BitNot: return 1;
				case SqlNodeType.Mul:
				case SqlNodeType.Div:
				case SqlNodeType.Mod: return 2;
				case SqlNodeType.Positive:
				case SqlNodeType.Negative:
				case SqlNodeType.Add:
				case SqlNodeType.Sub:
				case SqlNodeType.BitAnd:
				case SqlNodeType.BitOr:
				case SqlNodeType.BitXor: return 3;
				case SqlNodeType.EQ:
				case SqlNodeType.NotEQ:
				case SqlNodeType.GT:
				case SqlNodeType.GE:
				case SqlNodeType.LT:
				case SqlNodeType.LE: return 4;
				case SqlNodeType.Not: return 5;
				case SqlNodeType.And: return 6;
				case SqlNodeType.Between:
				case SqlNodeType.InList:
				case SqlNodeType.InSubquery:
				case SqlNodeType.Like:
				case SqlNodeType.Or:
				case SqlNodeType.Exists:
				case SqlNodeType.IsNull: return 7;
				default: throw new InvalidOperationException("unsupported operaion type");
			}
		}
		public SqlCommand Process(SqlNode node) {
			Visit(node);
			var res = new SqlCommand(Builder.ToString());
			res.Parameters.AddRange(Parameters.ToArray());
			return res;
		}
		protected override SqlTable VisitTable(SqlTable sql) {
			Builder.Append(" [")
				.Append(sql.TableName)
				.Append("]");
			var alias = GetAlias(sql);
			if (!string.IsNullOrEmpty(alias)) {
				Builder.Append(" AS ")
					.Append(alias);
			}
			return sql;
		}
		private int _aliasIndex;
		private readonly Dictionary<SqlSource, string> _sourceAliases = new Dictionary<SqlSource, string>();
		private string GetAlias(SqlSource sql) {
			string alias;
			if (!_sourceAliases.TryGetValue(sql, out alias)) {
				alias = string.Format("_t_{0}", _aliasIndex++);
				_sourceAliases[sql] = alias;
			}
			return alias;
		}
		private string GetAlias(SelectRef sql) {
			return GetAlias(sql.Reference);
		}
		protected override SqlParameter VisitParameter(SqlParameter sql) {
			var param = new System.Data.SqlClient.SqlParameter("@"+sql.Name, sql.Value);
			_parameters.Add(param);
			Builder.Append(param.ParameterName);
			/*
			throw new NotImplementedException();
			if (sql.Value == null || sql.Value is DBNull) {
				Builder.Append("NULL");
			} else {
				switch (sql.DbType) {

					case DbType.String:
						Builder.Append("N'")
							.Append(((string)sql.Value).Replace("'", "''"))
							.Append("'");
						break;
					case DbType.Binary:
						var arr = (byte[])sql.Value;
						Builder.Append("0x");
						for (int i = 0; i < arr.Length; i++) {
							Builder.AppendFormat("{0:x2}", arr[i]);
						}
						break;
					case DbType.Boolean:
						if ((bool)sql.Value) {
							Builder.Append("1");
						} else {
							Builder.Append("0");
						}
						break;
					case DbType.DateTime:
						Builder.AppendFormat("{0:o}", sql.Value);
						break;
					case DbType.Int32:
					case DbType.Byte:
					case DbType.Int64:
					case DbType.Int16:
					case DbType.Decimal:
					case DbType.Double:
					case DbType.Single:
						Builder.AppendFormat(CultureInfo.InvariantCulture, "{0}", sql.Value);
						break;
					default: throw new InvalidOperationException("Invalid SqlConstant type");
				}
			}
			*/
			return sql;
		}
		protected override SqlQuerySpecification VisitQuerySpecification(SqlQuerySpecification sql) {
			Builder.Append("SELECT ");
			Visit(sql.Columns);
			if (sql.TableSource != null) {
				Builder.Append(" FROM ");
				Visit(sql.TableSource);
			}
			if (sql.WhereClause != null) {
				Builder.Append(" WHERE ");
				Visit(sql.WhereClause);
			}
			if (sql.GroupClause != null) {
				Builder.Append(" GROUP BY ");
				Visit(sql.GroupClause);
			}
			return sql;
		}
		protected override SqlQueryExpression VisitQueryExpression(SqlQueryExpression sql) {
			Visit(sql.Top);
			Builder.Append(" ")
				.Append(GetNodeString(sql.NodeType))
				.Append(" ");
			if (sql.Bottom is SqlQueryExpression) {
				Builder.Append("(");
				Visit(sql.Bottom);
				Builder.Append(")");
			} else {
				Builder.Append(" ");
				Visit(sql.Bottom);
			}
			return sql;
		}
		protected override SqlColumn VisitColumn(SqlColumn sql) {
			var tableAlias=GetAlias(sql.SelectRef);
			if (!string.IsNullOrEmpty(tableAlias)){
				Builder.Append(tableAlias)
					.Append(".");
			}
			Builder.Append(sql.ColumnName);
			return sql;
		}
		protected override SelectItem VisitSelectItem(SelectItem sql) {
			Visit(sql.Expression);
			if (!string.IsNullOrEmpty(sql.ColumnAlias)){
				Builder.Append(" AS [")
					.Append(sql.ColumnAlias)
					.Append("]");
			}
			return sql;
		}
		protected override SqlSelectAll VisitSelectAll(SqlSelectAll sql) {
			Builder.Append("*");
			return sql;
		}
		protected override SqlSelect VisitSelect(SqlSelect sql) {
			Visit(sql.QueryExpression);
			if (sql.OrderBy != null) {
				Builder.Append(" ORDER BY ");
				bool comma = false;
				foreach (var orderItem in sql.OrderBy) {
					if (comma) {
						Builder.Append(", ");
					}
					Visit(orderItem);
				}
			}
			return sql;
		}
		protected override SqlJoin VisitJoin(SqlJoin sql) {
			Visit(sql.Left);
			Builder.Append(" ")
				.Append(GetJoinString(sql.JoinType))
				.Append(" ");
			Visit(sql.Right);
			if (sql.Condition!=null){
				Builder.Append(" ON ");
				Visit(sql.Condition);
			}
			return sql;
		}
		protected override SqlBinaryLogicalExpression VisitBinaryLogicalExpression(SqlBinaryLogicalExpression sql) {
			var currentLevel = GetLevel(sql);
			var leftLevel = GetLevel(sql.Left);
			var rightLevel = GetLevel(sql.Right);
			if (leftLevel > currentLevel) {
				Builder.Append("(");
			}
			Visit(sql.Left);
			if (leftLevel > currentLevel) {
				Builder.Append(")");
			}
			Builder.Append(" ")
				.Append(GetNodeString(sql.NodeType))
				.Append(" ");
			if (rightLevel > currentLevel) {
				Builder.Append("(");
			}
			Visit(sql.Right);
			if (rightLevel > currentLevel) {
				Builder.Append(")");
			}
			return sql;
		}
		protected override SqlComparision VisitComparision(SqlComparision sql) {
			Visit(sql.Left);
			Builder.Append(" ")
				.Append(GetNodeString(sql.NodeType))
				.Append(" ");
			Visit(sql.Right);
			return sql;
		}
		protected override SqlNot VisitNot(SqlNot sql) {
			if (_injectNot) {
				_injectNot = false; // NOT и NOT сократились
				Visit(sql.Inner);
			} else {
				if (sql.Inner.IsNotInjectionable) {
					_injectNot = true;
				} else {
					Builder.Append(" NOT ");
				}

				if (!(sql.Inner is SqlPredicate)) {
					Builder.Append("(");
					Visit(sql.Inner);
					Builder.Append(")");
				} else {
					Visit(sql.Inner);
				}
			}
			return sql;
		}
		protected override SqlIsNull VisitIsNull(SqlIsNull sql) {
			Visit(sql.Test);
			if (_injectNot) {
				Builder.Append(" IS NOT NULL ");
				_injectNot = false;
			} else {
				Builder.Append(" IS NULL ");
			}
			return sql;
		}
		protected override SqlLike VisitLike(SqlLike sql) {
			Visit(sql.Match);
			if (_injectNot) {
				Builder.Append(" NOT LIKE ");
				_injectNot = false;
			} else {
				Builder.Append(" LIKE ");
			}
			Visit(sql.Pattern);
			if (sql.Escape != null) {
				Builder.Append(" ESCAPE ");
				Visit(sql.Escape);
			}
			return sql;
		}
		protected override SqlBetween VisitBetween(SqlBetween sql) {
			Visit(sql.Test);
			if (_injectNot) {
				Builder.Append(" NOT BETWEEN ");
				_injectNot = false;
			} else {
				Builder.Append(" BETWEEN ");
			}
			Visit(sql.Begin);
			Builder.Append(" AND ");
			Visit(sql.End);
			return sql;
		}
		protected override SqlInList VisitInList(SqlInList sql) {
			Visit(sql.Test);
			if (_injectNot) {
				Builder.Append(" NOT IN (");
				_injectNot = false;
			} else {
				Builder.Append(" IN (");
			}
			var comma = false;
			foreach (var expr in sql.ExpressionList) {
				if (comma) {
					Builder.Append(", ");
				}
				Visit(expr);
				comma = true;
			}
			Builder.Append(")");
			return sql;
		}
		protected override SqlInSubquery VisitInSubquery(SqlInSubquery sql) {
			Visit(sql.Test);
			if (_injectNot) {
				Builder.Append(" NOT IN (");
				_injectNot = false;
			} else {
				Builder.Append(" IN (");
			}
			Visit(sql.SubQuery);
			Builder.Append(")");
			return sql;
		}
		protected override SqlExists VisitExists(SqlExists sql) {
			Builder.Append(" EXISTS (");
			Visit(sql.SubQuery);
			Builder.Append(")");
			return sql;
		}
		protected override SqlSubqueryComparision VisitSubqueryComparision(SqlSubqueryComparision sql) {
			Visit(sql.Test);
			Builder.Append(" ")
				.Append(GetNodeString(sql.NodeType));
			if (sql.ForAll) {
				Builder.Append(" ALL (");
			} else {
				Builder.Append(" SOME (");
			}
			Visit(sql.SubQuery);
			Builder.Append(")");
			return sql;
		}
		protected override SqlSelectCollection VisitSelectCollection(SqlSelectCollection sql) {
			var list = sql.SelectList;
			for (var i = 0; i < list.Count; i++) {
				var item = list[i];
				if (i > 0) {
					Builder.Append(", ");
				}
				Visit(item.Expression);
				if (!string.IsNullOrEmpty(item.ColumnAlias)) {
					Builder.Append(" AS [")
						.Append(item.ColumnAlias)
						.Append("]");
				}
			}
			return sql;
		}
		protected override SqlSelectItemRef VisitSelectItemRef(SqlSelectItemRef sql) {
			if (sql.SelectRef != null) {
				var selectAlias = GetAlias(sql.SelectRef);
				if (!string.IsNullOrEmpty(selectAlias)) {
					Builder.Append(selectAlias)
						.Append(".");
				}
			}
			Builder.Append("[")
				.Append(sql.SelectItem.ColumnAlias)
				.Append("]");
			return sql;
		}
		protected override SqlGroup VisitGroup(SqlGroup sql) {
			var list = sql.GroupList;
			for (var i = 0; i < list.Count; i++) {
				if (i > 0) {
					Builder.Append(", ");
				}
				Visit(list[i]);
			}
			return sql;
		}
		protected override SqlBinaryScalarExpression VisitBinaryScalarExpression(SqlBinaryScalarExpression sql) {
			var currentLevel = GetLevel(sql);
			var leftLevel = GetLevel(sql.Left);
			var rightLevel = GetLevel(sql.Right);
			if (leftLevel > currentLevel) {
				Builder.Append("(");
			}
			Visit(sql.Left);
			if (leftLevel > currentLevel) {
				Builder.Append(")");
			}
			Builder.Append(" ")
				.Append(GetNodeString(sql.NodeType))
				.Append(" ");
			if (rightLevel > currentLevel) {
				Builder.Append("(");
			}
			Visit(sql.Right);
			if (rightLevel > currentLevel) {
				Builder.Append(")");
			}
			return sql;
		}
		protected override SqlUnaryScalarOperaion VisitUnaryScalarOperaion(SqlUnaryScalarOperaion sql) {
			Builder.Append(GetNodeString(sql.NodeType));
			var needBeakets=sql.Inner is SqlBinaryScalarExpression;
			if (needBeakets){
				Builder.Append("(");
			}
			Visit(sql.Inner);
			if (needBeakets){
				Builder.Append(")");
			}
			return sql;
		}
		protected override SqlSubquery VisitSubquery(SqlSubquery sql) {
			Builder.Append("(");
			Visit(sql.Query);
			Builder.Append(")");
			var alias = GetAlias(sql);
			if (!string.IsNullOrEmpty(alias)){
				Builder.Append(" AS ")
					.Append(alias);
				
			}
			return sql;
		}
	}
}
